﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Genesis.Infrastructure.Domain
{

    public abstract class Entity<T> : IEquatable<Entity<T>>
    {

        private const int HASH_CODE_SEED = 31;

        private int? hashCode;

        public virtual T Id { get; protected set; }

        public bool Equals(Entity<T> other)
        {

            if (GetType() != other.GetType())
                return false;

            if (ReferenceEquals(other, null))
                return false;

            if (ReferenceEquals(this, other))
                return true;

            if (!IsTransient() && !other.IsTransient() && Equals(Id, other.Id))
                return true;

            if ((IsTransient() && !other.IsTransient()) || (!IsTransient() && other.IsTransient()))
                return false;
            
            IEnumerable<PropertyInfo> properties = PropertyCache.GetSignatureProperties(GetType());

            foreach (PropertyInfo property in properties)
            {

                object thisValue = property.GetValue(this, null);
                object otherValue = property.GetValue(other, null);

                if (!Equals(thisValue, otherValue))
                    return false;

            }

            return properties.Any() || base.Equals(other);

        }

        public override bool Equals(object obj)
        {

            return Equals(obj as Entity<T>);

        }

        public override int GetHashCode()
        {

            unchecked
            {

                if (!hashCode.HasValue)
                {

                    hashCode = GetType().GetHashCode();

                    if (!IsTransient())
                        hashCode = (hashCode * HASH_CODE_SEED) ^ Id.GetHashCode();

                    IEnumerable<PropertyInfo> properties = PropertyCache.GetSignatureProperties(GetType());

                    foreach (PropertyInfo property in properties)
                    {

                        object value = property.GetValue(this, null);

                        if (value != null)
                            hashCode = (hashCode * HASH_CODE_SEED) ^ value.GetHashCode();

                    }

                }

                return hashCode.Value;

            }

        }

        public virtual bool IsTransient()
        {

            return Equals(Id, null) || Equals(Id, default(T));

        }

        public static bool operator ==(Entity<T> primary, Entity<T> secondary)
        {

            return Equals(primary, secondary);

        }

        public static bool operator !=(Entity<T> primary, Entity<T> secondary)
        {

            return !Equals(primary, secondary);

        }

    }

}